package com.fourfinance.controller;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.fourfinance.exception.FormValidationException;
import com.fourfinance.exception.LenderException;
import com.fourfinance.model.domain.Client;
import com.fourfinance.model.domain.Loan;
import com.fourfinance.model.domain.error.FormValidationError;
import com.fourfinance.model.domain.error.LenderAppError;
import com.fourfinance.model.service.ClientService;
import com.fourfinance.model.service.LoanService;
import com.fourfinance.util.LenderConstants;
import com.fourfinance.util.LenderUtil;
import com.fourfinance.validation.LenderAppValidator;

@Controller
public class LenderController {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(LenderController.class);
	
	protected static final String VIEW_CLIENT = "client";
	protected static final String VIEW_LOAN = "loan";
	protected static final String VIEW_HISTORY = "history";
	protected static final String REDIRECT_LOANS = "redirect:/jsp/loans";

	@Resource
	private ClientService clientService;
	
	@Resource
	private LoanService loanService;
	
    @Resource
    private MessageSource messageSource;
    
    @Resource
    private Validator validator;  
    
	@InitBinder
	public void initBinder(WebDataBinder webDataBinder) {
		SimpleDateFormat dateFormat = new SimpleDateFormat(LenderConstants.DATE_FORMAT);
		dateFormat.setLenient(false);
		webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
	}
	
	@RequestMapping(value = "/json/apply", method = RequestMethod.POST)
	@ResponseBody
	public Loan applyLoan(@RequestBody Client client,
			@RequestHeader("amount") String amount,
			@RequestHeader("term") String term,
			HttpServletRequest request) throws LenderException, FormValidationException{
		
		String ipAddress = request.getRemoteAddr();
		
		validate("client", client);
		
		if(!LenderAppValidator.isTermInFuture(LenderUtil.convertStringToDate(term))){
			throw new LenderException(LenderUtil.getMessage("term.not.future"));
		}
		
		if(!LenderAppValidator.isAmountAllowed(Long.valueOf(amount))){
			throw new LenderException(LenderUtil.getMessage("rejection.bad.amount", LenderConstants.MAX_AMOUNT));
		}		
		
		Loan loan = clientService.applyLoan(client, ipAddress, 
				Long.valueOf(amount), LenderUtil.convertStringToDate(term));
		
		validate("loan", loan);
		
		if(loan == null){
			throw new LenderException(LenderUtil.getMessage("cannot.apply.loan"));
		}
		
		return loan;
	}
	
	@RequestMapping(value = "/json/extend", method = RequestMethod.POST)
	@ResponseBody
	public Loan extendLoan(@RequestParam String referenceId) throws LenderException{
		return clientService.extendLoan(referenceId);
	}
	
	@RequestMapping(value = "/json/history", method = RequestMethod.POST)
	@ResponseBody
	public Set<Loan> listLoans(@RequestBody Client client) {
		return clientService.listLoans(client);
	}
	
	@RequestMapping(value = "/json/list/clients", method = RequestMethod.GET)
	@ResponseBody
	public List<Client> listClients() {
		return clientService.list();
	}
	
	@RequestMapping(value = "/json/delete", method = RequestMethod.POST)
	@ResponseStatus(value = HttpStatus.OK)
	public void deleteClient(@RequestParam("email") String email) {
		clientService.remove(email);
	}
	
	@RequestMapping("/jsp/loans")
	public String listLoans(Map<String, Object> map, HttpSession session) {
		
		map.put("loan", new Loan());
		
		Set<Loan> loanList = null;
		if(session.getAttribute("client") != null){
			loanList = clientService.listLoans((Client)session.getAttribute("client"));
		}		
		map.put("loanList", loanList);
		
		return VIEW_LOAN;
	}
	
	@RequestMapping(value = "/jsp/apply", method = RequestMethod.POST)
	public String applyLoan(@ModelAttribute("loan") Loan loan, BindingResult result, RedirectAttributes redirectAttributes,
			HttpServletRequest request, HttpSession session) throws LenderException, FormValidationException {
		
		String ipAddress = request.getRemoteAddr();
		Client client = loan.getClient();
		Loan loanApplied = clientService.applyLoan(client, ipAddress, loan.getAmount(), loan.getTerm());
		if (loanApplied == null) {
			throw new LenderException(LenderUtil.getMessage("cannot.apply.loan"));
		}

		session.setAttribute("client", client);

		return REDIRECT_LOANS;
	}	
	
	@RequestMapping("/jsp/extend/{referenceId}")
	public String extendLoan(@PathVariable("referenceId") String referenceId, HttpSession session) throws LenderException {
		
		Loan loan = clientService.extendLoan(referenceId);
		Client client = loan.getClient();
		
		session.setAttribute("client", client);
		
		return REDIRECT_LOANS;
	}
	
	@RequestMapping("/jsp/history")
	public String history(@RequestParam(value = "email", required = false) String email, Map<String, Object> map) throws LenderException {
		
		//TODO: Better bad case handling with explanatory messages
		Client client = null;
		Set<Loan> loanList = null;
		if(email != null && email != ""){
			client = clientService.getByEmail(email);
			if(client != null){
				loanList = clientService.listLoans(client);	
			}
		}
		
		//client shouldn't sent with null value to the view 
		if(client == null){
			client = new Client();
		}
		
		map.put("client", client);		
		map.put("loanList", loanList);
		
		return VIEW_HISTORY;
	}
	
	@ExceptionHandler(LenderException.class)	
	@ResponseStatus(value = HttpStatus.BAD_REQUEST)
	@ResponseBody
	public LenderAppError handleException(LenderException ex, HttpServletRequest request, HttpServletResponse response) {
		
		LOGGER.debug("Request Content-Type: {}", request.getHeader("Content-Type"));
		
	    return new LenderAppError(ex.getLocalizedMessage());
	}
	
    @ExceptionHandler(FormValidationException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public FormValidationError handleFormValidationError(FormValidationException validationError) {
       
    	LOGGER.debug("Handling form validation error");

        List<FieldError> fieldErrors = validationError.getFieldErrors();
        FormValidationError error = new FormValidationError();

        for (FieldError fieldError: fieldErrors) {
        	error.addFieldError(fieldError.getField(), fieldError.getDefaultMessage());
        }

        return error;
    }
	
    private void validate(String objectName, Object validated) throws FormValidationException {
        
    	LOGGER.debug("Validating object: " + validated);

        BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(validated, objectName);
        validator.validate(validated, bindingResult);

        if (bindingResult.hasErrors()) {
            LOGGER.debug("Validation errors found:" + bindingResult.getFieldErrors());
            throw new FormValidationException(bindingResult.getFieldErrors());
        }
        
    }	
	
}
